﻿/*
 * LINQ to Active Directory
 * http://www.codeplex.com/LINQtoAD
 * 
 * Copyright Bart De Smet (C) 2007
 * info@bartdesmet.net - http://blogs.bartdesmet.net/bart
 * 
 * This project is subject to licensing restrictions. Visit http://www.codeplex.com/LINQtoAD/Project/License.aspx for more information.
 */

#region Namespace imports

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
//using System.DirectoryServices;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

#endregion

namespace Novell.DirectoryServices.Linq
{

    /// <summary>
    /// Represents an LDAP data source. Allows for querying the LDAP data source via LINQ.
    /// </summary>
    /// <typeparam name="T">Entity type in the underlying source.</typeparam>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
    public class DirectorySource<T> : IQueryable<T>, IDirectorySource
    {
        #region Private members

        /// <summary>
        /// Associated directory context if bound to a context; otherwise null.
        /// </summary>
        private DirectoryContext _context;
        /// <summary>
        /// Searcher object to perform the directory search. Captures the search root and options.
        /// </summary>
        private SearchOptions _searchOptions;

        ///// <summary>
        ///// Logger for diagnostic output.
        ///// </summary>
        //private TextWriter logger;

        ///// <summary>
        ///// Update catalog; keeps track of update entity instances.
        ///// </summary>
        //private Dictionary<object, HashSet<string>> updates = new Dictionary<object, HashSet<string>>();
        #endregion

        #region Constructors

        /// <summary>
        /// Creates a data source from the given context.
        /// </summary>
        /// <param name="context">DirectoryContext that embeds the data source.</param>
        /// <param name="searchScope">Search scope for all queries performed through this data source.</param>
        internal DirectorySource(DirectoryContext context, SearchOptions searchOptions)
        {
            _context = context;
            _searchOptions = searchOptions;
        }
        
        /// <summary>
        /// The initializer used when the source is retrieved and initialized by the framework
        /// </summary>
        /// <param name="parentSource"></param>
        /// <param name="searchScope"></param>
        internal DirectorySource(DirectoryEntity parentEntity, SearchScope searchScope)
        {
            _context = parentEntity.DirectorySource.Context;

            if (!parentEntity.IsNew)
            {
                //Pass the searcher
                _searchOptions = new SearchOptions(parentEntity.DirectoryEntry.DN, searchScope);
            }
            else
            {
                throw new NotImplementedException("Saving new objects that are children of new objects is not supported yet.");
            }
        }

        #endregion

        #region IDirectorySource
        /// <summary>
        /// Gets the context object for the directory source.
        /// </summary>
        public DirectoryContext Context
        {
            get { return _context; }
        }
        /// <summary>
        /// Used to configure a logger to print diagnostic information about the query performed.
        /// </summary>
        TextWriter IDirectorySource.Log
        {
            get
            {
                //if (logger != null)
                //    return logger;

                // Fall back to parent context if applicable.
                if (_context != null)
                    return _context.Log;

                return null;
            }
            //set
            //{
            //    // Can override on the leaf level.
            //    logger = value;
            //}
        }

        /// <summary>
        /// Search root.
        /// </summary>
        string IDirectorySource.Root
        {
            get { return this._searchOptions.SearchRoot; }
        }

        /// <summary>
        /// Search scope.
        /// </summary>
        SearchScope IDirectorySource.Scope
        {
            get { return this._searchOptions.Scope; }
        }
        /// <summary>
        /// Original type of objects being queried.
        /// </summary>
        Type IDirectorySource.OriginalType
        {
            get { return typeof(T); }
        }

        #endregion
        #region IQueryable<T>
        /// <summary>
        /// Entity element type.
        /// </summary>
        public Type ElementType
        {
            get { return typeof(T); }
        }

        /// <summary>
        /// Expression representing the data source object.
        /// </summary>
        public Expression Expression
        {
            get { return Expression.Constant(this); }
        }

        /// <summary>
        /// LINQ query provider object.
        /// </summary>
        public IQueryProvider Provider
        {
            //Should this return a consistent provider?  I think it should.
            get { return new DirectoryQueryProvider(); }
        }
        #endregion
        #region Enumeration

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        public IEnumerator<T> GetEnumerator()
        {
            return new DirectoryQuery<T>(this.Expression).GetEnumerator();
        }
        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return GetEnumerator();
        }
        #endregion
    }



}